package common;

import gui.OutputPanel;
import gui.PresentationControlPanel;
import gui.QueryRunPanel;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.TimeZone;
import org.jdom.JDOMException;
import storage.SamplingAccuracyInterpreter;
import storage.IDataStorage;
import storage.XmlDataStorage;
import storage.beans.GridBean;
import storage.beans.Region;
import storage.beans.SimulationBean;
import storage.statistics.MaxTemperatureStatistic;
import storage.statistics.MinTemperatureStatistic;
import storage.statistics.Statistic;
import storage.statistics.StatisticType;

public class EarthSimSettings {
  private static EarthSimSettings _instance = new EarthSimSettings();
  private SimpleDateFormat dateTimeFormat;
  private int precision = -1; //default floating point accuracy
  private int samplingRate = 100;
  private int temporalAccuracy = 100;  
  private int tilt = Constants.DEFAULT_TILT;
  private float eccentricity = Constants.DEFAULT_ECCENTRICITY;
  private int gridSpacing = Constants.DEFAULT_GRID_SPACING; //in degrees 
  private int timeStep = Constants.DEFAULT_SIM_TIME_STEP; //in minutes
  private int radius;
  private String startDate;
  private String startTime;
  private String endDate;
  private String endTime;  
  private boolean showingMinTemp = false;
  private boolean showingMaxTemp = false;
  private boolean showingMeanTempRegion = false;
  private boolean showingMeanTempTime = false;
  private boolean showingCellValues = false;
  private boolean animation = false;
  private boolean queryRunning = false;
  private Region region;
  private HashMap statistics;
  private List grids;
  private QueryRunPanel pnlRun;
  private OutputPanel pnlOutput;
  private IDataStorage storage;
  private PresentationControlPanel pnlPresentation;
  private final TimeZone timeZone = TimeZone.getTimeZone("GMT +0");
  private Calendar cal;
  private DecimalFormat decimal = new DecimalFormat();
  
  private EarthSimSettings() {
    statistics = new HashMap();
    region = new Region(180, 180, 180, 180);
    grids = new ArrayList();
    storage = new XmlDataStorage();
    cal = Calendar.getInstance(timeZone);
    dateTimeFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm");
    dateTimeFormat.setTimeZone(timeZone);
  }
  
  public static EarthSimSettings getInstance() {
    return _instance;
  }
  
  public Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException(); 
  }
  
  public TimeZone getTimeZone() {
    return timeZone;
  }
  
  public Calendar getCalendar() {
    return cal;
  }
  
  public boolean isQueryRunning() {
    return queryRunning;
  }

  public void setQueryRunning(boolean queryRunning) {
    this.queryRunning = queryRunning;
    pnlPresentation.setContols();
  }
  
  public void setPresentationPanel(PresentationControlPanel pnlPresentation) {
    this.pnlPresentation = pnlPresentation;    
  }

  public void resetRunButton() {
    pnlRun.resetRunButton();
  }
  
  public void setOutputPanel(OutputPanel pnlOutput) {
    this.pnlOutput = pnlOutput;
  }
  
  public void finalStep() {
    showStats();
    resetRunButton();
    saveSimulation();    
  }
  
  public void setRunPanelReference(QueryRunPanel pnlRun) {
    this.pnlRun = pnlRun;
  }
  
  public void addStatistic(Statistic stat) {
    statistics.put(stat.getType(), stat);
  }
  
  /**
   * @param type
   * @return null if the statistic has not been added
   */
  public Statistic getStatistic(StatisticType type) {
    return (Statistic)statistics.get(type);
  }
  
  public int getPrecision() {
    return precision;
  }

  public void setPrecision(int precision) {
    this.precision = precision;
        
    String formatPatternString = "0.";
    for (int i = 0; i < this.precision; i++) {
      formatPatternString += "0";
    }
    decimal = new DecimalFormat(formatPatternString);    
  }

  public int getSamplingRate() {
    return samplingRate;
  }

  public void setSamplingRate(int samplingRate) {
    this.samplingRate = samplingRate;
  }

  public int getTemporalAccuracy() {
    return temporalAccuracy;
  }

  public void setTemporalAccuracy(int temporalAccuracy) {
    this.temporalAccuracy = temporalAccuracy;
  }
    
  public int getRadius() {
    return radius;
  }
  
  /**
   * Radius of the earth in pixels (half the image size)
   * @param radius
   */
  public void setRadius(int radius) {
    this.radius = radius;
  }
  
  public float getEccentricity() {
    return eccentricity;
  }

  public void setEccentricity(float eccentricity) {
    this.eccentricity = eccentricity;
  }

  public int getGridSpacing() {
    return gridSpacing;
  }

  public void setGridSpacing(int gridSpacing) {
    this.gridSpacing = gridSpacing;
  }

  public long getDateTime(String date, String time) {
    try {
      Date d = dateTimeFormat.parse(date+" "+time);
      cal.setTime(d);      
    }
    catch (ParseException e) {
      e.printStackTrace();
    }
    return cal.getTimeInMillis();
  }
  
//  public String getDateString(long dateTime) {
//    cal.setTimeInMillis(dateTime);
//    cal.g
//  }
  
  public long getStartDateTime() {
    return getDateTime(startDate, startTime);
  }
  
  public long getEndDateTime() {
    return getDateTime(endDate, endTime);
  }

  public int getTilt() {
    return tilt;
  }

  public void setTilt(int tilt) {
    this.tilt = tilt;
  }

  public int getTimeStep() {
    return timeStep;
  }

  public void setTimeStep(int timeStep) {
    this.timeStep = timeStep;
  }
  
  public boolean isShowingCellValues() {
    return showingCellValues;
  }

  public void setShowingCellValues(boolean showingCellValues) {
    this.showingCellValues = showingCellValues;
  }

  public boolean isShowingMaxTemp() {
    return showingMaxTemp;
  }

  public void setShowingMaxTemp(boolean showingMaxTemp) {
    this.showingMaxTemp = showingMaxTemp;
  }

  public boolean isShowingMeanTempRegion() {
    return showingMeanTempRegion;
  }

  public void setShowingMeanTempRegion(boolean showingMeanTempRegion) {
    this.showingMeanTempRegion = showingMeanTempRegion;
  }

  public boolean isShowingMeanTempTime() {
    return showingMeanTempTime;
  }

  public void setShowingMeanTempTime(boolean showingMeanTempTime) {
    this.showingMeanTempTime = showingMeanTempTime;
  }

  public boolean isShowingMinTemp() {
    return showingMinTemp;
  }

  public void setShowingMinTemp(boolean showingMinTemp) {
    this.showingMinTemp = showingMinTemp;
  }
  
  public boolean isAnimation() {
    return animation;
  }

  public void setAnimation(boolean animation) {
    this.animation = animation;
  }
  
  public int getLatNorth() {
    return region.getLatNorth();
  }

  public void setLatNorth(int latNorth) {
    this.region.setLatNorth(latNorth);
  }

  public int getLatSouth() {
    return region.getLatSouth();
  }

  public void setLatSouth(int latSouth) {
    this.region.setLatSouth(latSouth);
  }

  public int getLongEast() {
    return region.getLongEast();
  }

  public void setLongEast(int longEast) {
    this.region.setLongEast(longEast);
  }

  public int getLongWest() {
    return region.getLongWest();
  }

  public void setLongWest(int longWest) {
    this.region.setLongWest(longWest);
  }
  
  public void setEndDate(String endDate) {
    this.endDate = endDate;
  }

  public void setEndTime(String endTime) {
    this.endTime = endTime;
  }

  public void setStartDate(String startDate) {
    this.startDate = startDate;
  }

  public void setStartTime(String startTime) {
    this.startTime = startTime;
  }
  
  public void cacheGrid(IGrid grid) {
    grids.add(grid.copy());    
  }

  public void hideStats() {
    statistics.clear();
    pnlOutput.update();
  }
  
  public void showStats() {
//    double minTemp = 0;
//    double maxTemp = 0;
//    double meanTempRegion = 0;
//    double meanTempTime = 0;
    
    MaxTemperatureStatistic maxTempStat = new MaxTemperatureStatistic(Constants.DEFAULT_CELL_TEMP, region, getStartDateTime());
    MinTemperatureStatistic minTempStat = new MinTemperatureStatistic(Constants.DEFAULT_CELL_TEMP, region, getStartDateTime());
    
    for(int i = 0; i < grids.size(); i++) {
      IGrid grid = (IGrid) grids.get(i);
      for (int y = 0; y < grid.getHeight(); y++) {
        for (int x = 0; x < grid.getWidth(); x++) {
          double temp = grid.getTemperature(x, y);
          if(temp < minTempStat.getTemperature())
            minTempStat = new MinTemperatureStatistic(temp, grid.getRegion(x, y), grid.getTime());
          else if(temp >= maxTempStat.getTemperature())
            maxTempStat = new MaxTemperatureStatistic(temp, grid.getRegion(x, y), grid.getTime());          
        }
      }
    }
    
    if(showingMaxTemp)
      statistics.put(StatisticType.MAX_TEMP, maxTempStat);
    if(showingMinTemp)
      statistics.put(StatisticType.MIN_TEMP, minTempStat);
    
    pnlOutput.update();
  }
  
  public DecimalFormat getDecimalFormat() {
    return decimal;
  }
  
  public List getCachedGrids() {
    return grids;
  }
  
  public SimulationBean getSimulationBean() {
    SimulationBean bean = new SimulationBean();
    bean.setEccentricity(eccentricity);
    bean.setGridSpacing(gridSpacing);
    bean.setTilt(tilt);
    bean.setTimeStep(timeStep);
    bean.setSamplingRate(samplingRate);
    bean.setTemporalAccuracy(temporalAccuracy);
    bean.setPrecision(precision);
    bean.setRadius(radius);
    bean.setRegion(region);
    
    long startDateTime = getDateTime(startDate, startTime);
    long endDateTime = getDateTime(endDate, endTime);
    bean.setStartDateTime(startDateTime);
    bean.setEndDateTime(endDateTime);
    
    //compute temporal accuracy
    long duration = endDateTime - startDateTime;
    long stepsToTake = duration / timeStep;
    double stepsToSkip = stepsToTake * getTemporalAccuracy() / 100;
    int interval = (int) Math.ceil(stepsToTake / stepsToSkip) - 1;
    int intervalCounter = 0;
    
    SamplingAccuracyInterpreter ai = new SamplingAccuracyInterpreter(samplingRate);
    for(int i = 0; i < grids.size(); i++) {
      IGrid grid = (IGrid) grids.get(i);
      if(intervalCounter++ == interval) {
        bean.addSnapshot(new GridBean(grid.getTime(), grid.getWidth(), grid.getHeight(), ai.getGrid(grid)));
        intervalCounter = 0;
      }
    }
    
    bean.setTimestamp(new Date().getTime());
    return bean;
  }
  
  public void saveSimulation() {
    try {
      storage.saveSimulation(getSimulationBean());
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
  
  public List getSimulations() throws IOException, JDOMException {
    return storage.getSimulations(tilt, eccentricity);
  }
  
  public void reset() {
    grids.clear();
  }
  
  public String toString() {
    String s = 
      "\n----Invocation Settings----"+
      "\nSampling Rate: "+samplingRate +
      "\nTemporal Acc: "+temporalAccuracy + 
      "\nPrecision: "+ (precision == -1 ? "default" : String.valueOf(precision)) +
      
      "\n\n----Simulation Settings----"+
      "\nGrid Spacing: "+gridSpacing +
      "\nTime Step: "+timeStep+
      "\nRadius: "+radius +
      
      "\n\n----Physical Settings----"+
      "\nEccentricity: "+eccentricity +
      "\nTilt: "+ tilt +
      
      "\n\n----Date/Time Settings----"+
      "\nStart: "+dateTimeFormat.format(new Date(getStartDateTime()))+
      "\nEnd: "+dateTimeFormat.format(new Date(getEndDateTime()))+
    
      "\n\n----Region Settings----"+
      "\nLongitude: "+region.getLongEast()+"E / "+region.getLongWest()+"W"+
      "\nLatitude: "+region.getLatNorth()+"N / "+region.getLatSouth()+"S"
    
    ;
    return s;      
  }

  
}
